home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / iceweasel / modules / TreePanel.jsm < prev   
Encoding:
Text File  |  2013-01-09  |  25.5 KB  |  842 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
  3. /* ***** BEGIN LICENSE BLOCK *****
  4.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  5.  *
  6.  * The contents of this file are subject to the Mozilla Public License Version
  7.  * 1.1 (the "License"); you may not use this file except in compliance with
  8.  * the License. You may obtain a copy of the License at
  9.  * http://www.mozilla.org/MPL/
  10.  *
  11.  * Software distributed under the License is distributed on an "AS IS" basis,
  12.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  13.  * for the specific language governing rights and limitations under the
  14.  * License.
  15.  *
  16.  * The Original Code is the Mozilla Tree Panel.
  17.  *
  18.  * The Initial Developer of the Original Code is
  19.  * The Mozilla Foundation.
  20.  * Portions created by the Initial Developer are Copyright (C) 2011
  21.  * the Initial Developer. All Rights Reserved.
  22.  *
  23.  * Contributor(s):
  24.  *   Rob Campbell <rcampbell@mozilla.com> (original author)
  25.  *   Mihai ╚ÿucan <mihai.sucan@gmail.com>
  26.  *   Julian Viereck <jviereck@mozilla.com>
  27.  *   Paul Rouget <paul@mozilla.com>
  28.  *   Kyle Simpson <getify@mozilla.com>
  29.  *
  30.  * Alternatively, the contents of this file may be used under the terms of
  31.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  32.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  33.  * in which case the provisions of the GPL or the LGPL are applicable instead
  34.  * of those above. If you wish to allow use of your version of this file only
  35.  * under the terms of either the GPL or the LGPL, and not to allow others to
  36.  * use your version of this file under the terms of the MPL, indicate your
  37.  * decision by deleting the provisions above and replace them with the notice
  38.  * and other provisions required by the GPL or the LGPL. If you do not delete
  39.  * the provisions above, a recipient may use your version of this file under
  40.  * the terms of any one of the MPL, the GPL or the LGPL.
  41.  *
  42.  * ***** END LICENSE BLOCK ***** */
  43.  
  44. const Cu = Components.utils;
  45.  
  46. Cu.import("resource:///modules/domplate.jsm");
  47. Cu.import("resource:///modules/InsideOutBox.jsm");
  48. Cu.import("resource://gre/modules/Services.jsm");
  49.  
  50. var EXPORTED_SYMBOLS = ["TreePanel", "DOMHelpers"];
  51.  
  52. const INSPECTOR_URI = "chrome://browser/content/inspector.html";
  53.  
  54. /**
  55.  * TreePanel
  56.  * A container for the Inspector's HTML Tree Panel widget constructor function.
  57.  * @param aContext nsIDOMWindow (xulwindow)
  58.  * @param aIUI global InspectorUI object
  59.  */
  60. function TreePanel(aContext, aIUI) {
  61.   this._init(aContext, aIUI);
  62. };
  63.  
  64. TreePanel.prototype = {
  65.   showTextNodesWithWhitespace: false,
  66.   id: "treepanel", // DO NOT LOCALIZE
  67.   openInDock: true,
  68.  
  69.   /**
  70.    * The tree panel container element.
  71.    * @returns xul:panel|xul:vbox|null
  72.    *          xul:panel is returned when the tree panel is not docked, or
  73.    *          xul:vbox when when the tree panel is docked.
  74.    *          null is returned when no container is available.
  75.    */
  76.   get container()
  77.   {
  78.     if (this.openInDock) {
  79.       return this.document.getElementById("inspector-tree-box");
  80.     }
  81.  
  82.     return this.document.getElementById("inspector-tree-panel");
  83.   },
  84.  
  85.   /**
  86.    * Main TreePanel boot-strapping method. Initialize the TreePanel with the
  87.    * originating context and the InspectorUI global.
  88.    * @param aContext nsIDOMWindow (xulwindow)
  89.    * @param aIUI global InspectorUI object
  90.    */
  91.   _init: function TP__init(aContext, aIUI)
  92.   {
  93.     this.IUI = aIUI;
  94.     this.window = aContext;
  95.     this.document = this.window.document;
  96.  
  97.     domplateUtils.setDOM(this.window);
  98.  
  99.     this.DOMHelpers = new DOMHelpers(this.window);
  100.  
  101.     let isOpen = this.isOpen.bind(this);
  102.  
  103.     this.registrationObject = {
  104.       id: this.id,
  105.       label: this.IUI.strings.GetStringFromName("htmlPanel.label"),
  106.       tooltiptext: this.IUI.strings.GetStringFromName("htmlPanel.tooltiptext"),
  107.       accesskey: this.IUI.strings.GetStringFromName("htmlPanel.accesskey"),
  108.       context: this,
  109.       get isOpen() isOpen(),
  110.       show: this.open,
  111.       hide: this.close,
  112.       onSelect: this.select,
  113.       panel: this.openInDock ? null : this.container,
  114.       unregister: this.destroy,
  115.     };
  116.     this.editingEvents = {};
  117.  
  118.     if (!this.openInDock) {
  119.       this._boundClose = this.close.bind(this);
  120.       this.container.addEventListener("popuphiding", this._boundClose, false);
  121.     }
  122.  
  123.     // Register the HTML panel with the highlighter
  124.     this.IUI.registerTool(this.registrationObject);
  125.   },
  126.  
  127.   /**
  128.    * Initialization function for the TreePanel.
  129.    */
  130.   initializeIFrame: function TP_initializeIFrame()
  131.   {
  132.     if (!this.initializingTreePanel || this.treeLoaded) {
  133.       return;
  134.     }
  135.     this.treeBrowserDocument = this.treeIFrame.contentDocument;
  136.     this.treePanelDiv = this.treeBrowserDocument.createElement("div");
  137.     this.treeBrowserDocument.body.appendChild(this.treePanelDiv);
  138.     this.treePanelDiv.ownerPanel = this;
  139.     this.ioBox = new InsideOutBox(this, this.treePanelDiv);
  140.     this.ioBox.createObjectBox(this.IUI.win.document.documentElement);
  141.     this.treeLoaded = true;
  142.     this.treeIFrame.addEventListener("click", this.onTreeClick.bind(this), false);
  143.     this.treeIFrame.addEventListener("dblclick", this.onTreeDblClick.bind(this), false);
  144.     this.treeIFrame.addEventListener("keypress", this.IUI, false);
  145.     delete this.initializingTreePanel;
  146.     Services.obs.notifyObservers(null,
  147.       this.IUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, null);
  148.     if (this.IUI.selection)
  149.       this.select(this.IUI.selection, true);
  150.   },
  151.  
  152.   /**
  153.    * Open the inspector's tree panel and initialize it.
  154.    */
  155.   open: function TP_open()
  156.   {
  157.     if (this.initializingTreePanel && !this.treeLoaded) {
  158.       return;
  159.     }
  160.  
  161.     this.initializingTreePanel = true;
  162.     if (!this.openInDock)
  163.       this.container.hidden = false;
  164.  
  165.     this.treeIFrame = this.document.getElementById("inspector-tree-iframe");
  166.     if (!this.treeIFrame) {
  167.       this.treeIFrame = this.document.createElement("iframe");
  168.       this.treeIFrame.setAttribute("id", "inspector-tree-iframe");
  169.       this.treeIFrame.flex = 1;
  170.       this.treeIFrame.setAttribute("type", "content");
  171.     }
  172.  
  173.     if (this.openInDock) { // Create vbox
  174.       this.openDocked();
  175.       return;
  176.     }
  177.  
  178.     let resizerBox = this.document.getElementById("tree-panel-resizer-box");
  179.     this.treeIFrame = this.container.insertBefore(this.treeIFrame, resizerBox);
  180.  
  181.     let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
  182.     {
  183.       this.treeIFrame.removeEventListener("load",
  184.         boundLoadedInitializeTreePanel, true);
  185.       this.initializeIFrame();
  186.     }.bind(this);
  187.  
  188.     let boundTreePanelShown = function treePanelShown()
  189.     {
  190.       this.container.removeEventListener("popupshown",
  191.         boundTreePanelShown, false);
  192.  
  193.       this.treeIFrame.addEventListener("load",
  194.         boundLoadedInitializeTreePanel, true);
  195.  
  196.       let src = this.treeIFrame.getAttribute("src");
  197.       if (src != INSPECTOR_URI) {
  198.         this.treeIFrame.setAttribute("src", INSPECTOR_URI);
  199.       } else {
  200.         this.treeIFrame.contentWindow.location.reload();
  201.       }
  202.     }.bind(this);
  203.  
  204.     this.container.addEventListener("popupshown", boundTreePanelShown, false);
  205.  
  206.     const panelWidthRatio = 7 / 8;
  207.     const panelHeightRatio = 1 / 5;
  208.  
  209.     let width = parseInt(this.IUI.win.outerWidth * panelWidthRatio);
  210.     let height = parseInt(this.IUI.win.outerHeight * panelHeightRatio);
  211.     let y = Math.min(this.document.defaultView.screen.availHeight - height,
  212.       this.IUI.win.innerHeight);
  213.  
  214.     this.container.openPopup(this.browser, "overlap", 0, 0,
  215.       false, false);
  216.  
  217.     this.container.moveTo(80, y);
  218.     this.container.sizeTo(width, height);
  219.   },
  220.  
  221.   openDocked: function TP_openDocked()
  222.   {
  223.     let treeBox = null;
  224.     let toolbar = this.IUI.toolbar.nextSibling; // Addons bar, typically
  225.     let toolbarParent =
  226.       this.IUI.browser.ownerDocument.getElementById("browser-bottombox");
  227.     treeBox = this.document.createElement("vbox");
  228.     treeBox.id = "inspector-tree-box";
  229.     treeBox.state = "open"; // for the registerTools API.
  230.     try {
  231.       treeBox.height =
  232.         Services.prefs.getIntPref("devtools.inspector.htmlHeight");
  233.     } catch(e) {
  234.       treeBox.height = 112;
  235.     }
  236.                       
  237.     treeBox.minHeight = 64;
  238.     treeBox.flex = 1;
  239.     toolbarParent.insertBefore(treeBox, toolbar);
  240.  
  241.     this.IUI.toolbar.setAttribute("treepanel-open", "true");
  242.  
  243.     treeBox.appendChild(this.treeIFrame);
  244.  
  245.     let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
  246.     {
  247.       this.treeIFrame.removeEventListener("load",
  248.         boundLoadedInitializeTreePanel, true);
  249.       this.initializeIFrame();
  250.     }.bind(this);
  251.  
  252.     this.treeIFrame.addEventListener("load",
  253.       boundLoadedInitializeTreePanel, true);
  254.  
  255.     let src = this.treeIFrame.getAttribute("src");
  256.     if (src != INSPECTOR_URI) {
  257.       this.treeIFrame.setAttribute("src", INSPECTOR_URI);
  258.     } else {
  259.       this.treeIFrame.contentWindow.location.reload();
  260.     }
  261.   },
  262.  
  263.   /**
  264.    * Close the TreePanel.
  265.    */
  266.   close: function TP_close()
  267.   {
  268.     if (this.openInDock) {
  269.       this.IUI.toolbar.removeAttribute("treepanel-open");
  270.  
  271.       let treeBox = this.container;
  272.       Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height);
  273.       let treeBoxParent = treeBox.parentNode;
  274.       treeBoxParent.removeChild(treeBox);
  275.     } else {
  276.       this.container.hidePopup();
  277.     }
  278.  
  279.     if (this.treePanelDiv) {
  280.       this.treePanelDiv.ownerPanel = null;
  281.       let parent = this.treePanelDiv.parentNode;
  282.       parent.removeChild(this.treePanelDiv);
  283.       delete this.treePanelDiv;
  284.       delete this.treeBrowserDocument;
  285.     }
  286.  
  287.     this.treeLoaded = false;
  288.   },
  289.  
  290.   /**
  291.    * Is the TreePanel open?
  292.    * @returns boolean
  293.    */
  294.   isOpen: function TP_isOpen()
  295.   {
  296.     if (this.openInDock)
  297.       return this.treeLoaded && this.container;
  298.  
  299.     return this.treeLoaded && this.container.state == "open";
  300.   },
  301.  
  302.   /**
  303.    * Create the ObjectBox for the given object.
  304.    * @param object nsIDOMNode
  305.    * @param isRoot boolean - Is this the root object?
  306.    * @returns InsideOutBox
  307.    */
  308.   createObjectBox: function TP_createObjectBox(object, isRoot)
  309.   {
  310.     let tag = domplateUtils.getNodeTag(object);
  311.     if (tag)
  312.       return tag.replace({object: object}, this.treeBrowserDocument);
  313.   },
  314.  
  315.   getParentObject: function TP_getParentObject(node)
  316.   {
  317.     return this.DOMHelpers.getParentObject(node);
  318.   },
  319.  
  320.   getChildObject: function TP_getChildObject(node, index, previousSibling)
  321.   {
  322.     return this.DOMHelpers.getChildObject(node, index, previousSibling,
  323.                                         this.showTextNodesWithWhitespace);
  324.   },
  325.  
  326.   getFirstChild: function TP_getFirstChild(node)
  327.   {
  328.     return this.DOMHelpers.getFirstChild(node);
  329.   },
  330.  
  331.   getNextSibling: function TP_getNextSibling(node)
  332.   {
  333.     return this.DOMHelpers.getNextSibling(node);
  334.   },
  335.  
  336.   /////////////////////////////////////////////////////////////////////
  337.   // Event Handling
  338.  
  339.   /**
  340.    * Handle click events in the html tree panel.
  341.    * @param aEvent
  342.    *        The mouse event.
  343.    */
  344.   onTreeClick: function TP_onTreeClick(aEvent)
  345.   {
  346.     let node;
  347.     let target = aEvent.target;
  348.     let hitTwisty = false;
  349.     if (this.hasClass(target, "twisty")) {
  350.       node = this.getRepObject(aEvent.target.nextSibling);
  351.       hitTwisty = true;
  352.     } else {
  353.       node = this.getRepObject(aEvent.target);
  354.     }
  355.  
  356.     if (node) {
  357.       if (hitTwisty) {
  358.         this.ioBox.toggleObject(node);
  359.       } else {
  360.         if (this.IUI.inspecting) {
  361.           this.IUI.stopInspecting(true);
  362.         } else {
  363.           this.IUI.select(node, true, false);
  364.           this.IUI.highlighter.highlightNode(node);
  365.         }
  366.       }
  367.     }
  368.   },
  369.  
  370.   /**
  371.    * Handle double-click events in the html tree panel.
  372.    * (double-clicking an attribute value allows it to be edited)
  373.    * @param aEvent
  374.    *        The mouse event.
  375.    */
  376.   onTreeDblClick: function TP_onTreeDblClick(aEvent)
  377.   {
  378.     // if already editing an attribute value, double-clicking elsewhere
  379.     // in the tree is the same as a click, which dismisses the editor
  380.     if (this.editingContext)
  381.       this.closeEditor();
  382.  
  383.     let target = aEvent.target;
  384.  
  385.     if (this.hasClass(target, "nodeValue")) {
  386.       let repObj = this.getRepObject(target);
  387.       let attrName = target.getAttribute("data-attributeName");
  388.       let attrVal = target.innerHTML;
  389.  
  390.       this.editAttributeValue(target, repObj, attrName, attrVal);
  391.     }
  392.   },
  393.  
  394.   /**
  395.    * Starts the editor for an attribute value.
  396.    * @param aAttrObj
  397.    *        The DOM object representing the attribute value in the HTML Tree
  398.    * @param aRepObj
  399.    *        The original DOM (target) object being inspected/edited
  400.    * @param aAttrName
  401.    *        The name of the attribute being edited
  402.    * @param aAttrVal
  403.    *        The current value of the attribute being edited
  404.    */
  405.   editAttributeValue:
  406.   function TP_editAttributeValue(aAttrObj, aRepObj, aAttrName, aAttrVal)
  407.   {
  408.     let editor = this.treeBrowserDocument.getElementById("attribute-editor");
  409.     let editorInput =
  410.       this.treeBrowserDocument.getElementById("attribute-editor-input");
  411.     let attrDims = aAttrObj.getBoundingClientRect();
  412.     // figure out actual viewable viewport dimensions (sans scrollbars)
  413.     let viewportWidth = this.treeBrowserDocument.documentElement.clientWidth;
  414.     let viewportHeight = this.treeBrowserDocument.documentElement.clientHeight;
  415.  
  416.     // saves the editing context for use when the editor is saved/closed
  417.     this.editingContext = {
  418.       attrObj: aAttrObj,
  419.       repObj: aRepObj,
  420.       attrName: aAttrName
  421.     };
  422.  
  423.     // highlight attribute-value node in tree while editing
  424.     this.addClass(aAttrObj, "editingAttributeValue");
  425.  
  426.     // show the editor
  427.     this.addClass(editor, "editing");
  428.  
  429.     // offset the editor below the attribute-value node being edited
  430.     let editorVeritcalOffset = 2;
  431.  
  432.     // keep the editor comfortably within the bounds of the viewport
  433.     let editorViewportBoundary = 5;
  434.  
  435.     // outer editor is sized based on the <input> box inside it
  436.     editorInput.style.width = Math.min(attrDims.width, viewportWidth -
  437.                                 editorViewportBoundary) + "px";
  438.     editorInput.style.height = Math.min(attrDims.height, viewportHeight -
  439.                                 editorViewportBoundary) + "px";
  440.     let editorDims = editor.getBoundingClientRect();
  441.  
  442.     // calculate position for the editor according to the attribute node
  443.     let editorLeft = attrDims.left + this.treeIFrame.contentWindow.scrollX -
  444.                     // center the editor against the attribute value
  445.                     ((editorDims.width - attrDims.width) / 2);
  446.     let editorTop = attrDims.top + this.treeIFrame.contentWindow.scrollY +
  447.                     attrDims.height + editorVeritcalOffset;
  448.  
  449.     // but, make sure the editor stays within the visible viewport
  450.     editorLeft = Math.max(0, Math.min(
  451.                                       (this.treeIFrame.contentWindow.scrollX +
  452.                                           viewportWidth - editorDims.width),
  453.                                       editorLeft)
  454.                           );
  455.     editorTop = Math.max(0, Math.min(
  456.                                       (this.treeIFrame.contentWindow.scrollY +
  457.                                           viewportHeight - editorDims.height),
  458.                                       editorTop)
  459.                           );
  460.  
  461.     // position the editor
  462.     editor.style.left = editorLeft + "px";
  463.     editor.style.top = editorTop + "px";
  464.  
  465.     // set and select the text
  466.     editorInput.value = aAttrVal;
  467.     editorInput.select();
  468.  
  469.     // remove tree key navigation events
  470.     this.treeIFrame.removeEventListener("keypress", this.IUI, false);
  471.  
  472.     // listen for editor specific events
  473.     this.bindEditorEvent(editor, "click", function(aEvent) {
  474.       aEvent.stopPropagation();
  475.     });
  476.     this.bindEditorEvent(editor, "dblclick", function(aEvent) {
  477.       aEvent.stopPropagation();
  478.     });
  479.     this.bindEditorEvent(editor, "keypress",
  480.                           this.handleEditorKeypress.bind(this));
  481.  
  482.     // event notification
  483.     Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED,
  484.                                   null);
  485.   },
  486.  
  487.   /**
  488.    * Handle binding an event handler for the editor.
  489.    * (saves the callback for easier unbinding later)
  490.    * @param aEditor
  491.    *        The DOM object for the editor
  492.    * @param aEventName
  493.    *        The name of the event to listen for
  494.    * @param aEventCallback
  495.    *        The callback to bind to the event (and also to save for later
  496.    *          unbinding)
  497.    */
  498.   bindEditorEvent:
  499.   function TP_bindEditorEvent(aEditor, aEventName, aEventCallback)
  500.   {
  501.     this.editingEvents[aEventName] = aEventCallback;
  502.     aEditor.addEventListener(aEventName, aEventCallback, false);
  503.   },
  504.  
  505.   /**
  506.    * Handle unbinding an event handler from the editor.
  507.    * (unbinds the previously bound and saved callback)
  508.    * @param aEditor
  509.    *        The DOM object for the editor
  510.    * @param aEventName
  511.    *        The name of the event being listened for
  512.    */
  513.   unbindEditorEvent: function TP_unbindEditorEvent(aEditor, aEventName)
  514.   {
  515.     aEditor.removeEventListener(aEventName, this.editingEvents[aEventName],
  516.                                   false);
  517.     this.editingEvents[aEventName] = null;
  518.   },
  519.  
  520.   /**
  521.    * Handle keypress events in the editor.
  522.    * @param aEvent
  523.    *        The keyboard event.
  524.    */
  525.   handleEditorKeypress: function TP_handleEditorKeypress(aEvent)
  526.   {
  527.     if (aEvent.which == this.window.KeyEvent.DOM_VK_RETURN) {
  528.       this.saveEditor();
  529.       aEvent.preventDefault();
  530.       aEvent.stopPropagation();
  531.     } else if (aEvent.keyCode == this.window.KeyEvent.DOM_VK_ESCAPE) {
  532.       this.closeEditor();
  533.       aEvent.preventDefault();
  534.       aEvent.stopPropagation();
  535.     }
  536.   },
  537.  
  538.   /**
  539.    * Close the editor and cleanup.
  540.    */
  541.   closeEditor: function TP_closeEditor()
  542.   {
  543.     let editor = this.treeBrowserDocument.getElementById("attribute-editor");
  544.     let editorInput =
  545.       this.treeBrowserDocument.getElementById("attribute-editor-input");
  546.  
  547.     // remove highlight from attribute-value node in tree
  548.     this.removeClass(this.editingContext.attrObj, "editingAttributeValue");
  549.  
  550.     // hide editor
  551.     this.removeClass(editor, "editing");
  552.  
  553.     // stop listening for editor specific events
  554.     this.unbindEditorEvent(editor, "click");
  555.     this.unbindEditorEvent(editor, "dblclick");
  556.     this.unbindEditorEvent(editor, "keypress");
  557.  
  558.     // clean up after the editor
  559.     editorInput.value = "";
  560.     editorInput.blur();
  561.     this.editingContext = null;
  562.     this.editingEvents = {};
  563.  
  564.     // re-add navigation listener
  565.     this.treeIFrame.addEventListener("keypress", this.IUI, false);
  566.  
  567.     // event notification
  568.     Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED,
  569.                                   null);
  570.   },
  571.  
  572.   /**
  573.    * Commit the edits made in the editor, then close it.
  574.    */
  575.   saveEditor: function TP_saveEditor()
  576.   {
  577.     let editorInput =
  578.       this.treeBrowserDocument.getElementById("attribute-editor-input");
  579.  
  580.     // set the new attribute value on the original target DOM element
  581.     this.editingContext.repObj.setAttribute(this.editingContext.attrName,
  582.                                               editorInput.value);
  583.  
  584.     // update the HTML tree attribute value
  585.     this.editingContext.attrObj.innerHTML = editorInput.value;
  586.  
  587.     this.IUI.isDirty = true;
  588.     this.IUI.nodeChanged(this.registrationObject);
  589.  
  590.     // event notification
  591.     Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED,
  592.                                   null);
  593.  
  594.     this.closeEditor();
  595.   },
  596.  
  597.   /**
  598.    * Simple tree select method.
  599.    * @param aNode the DOM node in the content document to select.
  600.    * @param aScroll boolean scroll to the visible node?
  601.    */
  602.   select: function TP_select(aNode, aScroll)
  603.   {
  604.     if (this.ioBox)
  605.       this.ioBox.select(aNode, true, true, aScroll);
  606.   },
  607.  
  608.   ///////////////////////////////////////////////////////////////////////////
  609.   //// Utility functions
  610.  
  611.   /**
  612.    * Does the given object have a class attribute?
  613.    * @param aNode
  614.    *        the DOM node.
  615.    * @param aClass
  616.    *        The class string.
  617.    * @returns boolean
  618.    */
  619.   hasClass: function TP_hasClass(aNode, aClass)
  620.   {
  621.     if (!(aNode instanceof this.window.Element))
  622.       return false;
  623.     return aNode.classList.contains(aClass);
  624.   },
  625.  
  626.   /**
  627.    * Add the class name to the given object.
  628.    * @param aNode
  629.    *        the DOM node.
  630.    * @param aClass
  631.    *        The class string.
  632.    */
  633.   addClass: function TP_addClass(aNode, aClass)
  634.   {
  635.     if (aNode instanceof this.window.Element)
  636.       aNode.classList.add(aClass);
  637.   },
  638.  
  639.   /**
  640.    * Remove the class name from the given object
  641.    * @param aNode
  642.    *        the DOM node.
  643.    * @param aClass
  644.    *        The class string.
  645.    */
  646.   removeClass: function TP_removeClass(aNode, aClass)
  647.   {
  648.     if (aNode instanceof this.window.Element)
  649.       aNode.classList.remove(aClass);
  650.   },
  651.  
  652.   /**
  653.    * Get the "repObject" from the HTML panel's domplate-constructed DOM node.
  654.    * In this system, a "repObject" is the Object being Represented by the box
  655.    * object. It is the "real" object that we're building our facade around.
  656.    *
  657.    * @param element
  658.    *        The element in the HTML panel the user clicked.
  659.    * @returns either a real node or null
  660.    */
  661.   getRepObject: function TP_getRepObject(element)
  662.   {
  663.     let target = null;
  664.     for (let child = element; child; child = child.parentNode) {
  665.       if (this.hasClass(child, "repTarget"))
  666.         target = child;
  667.  
  668.       if (child.repObject) {
  669.         if (!target && this.hasClass(child.repObject, "repIgnore"))
  670.           break;
  671.         else
  672.           return child.repObject;
  673.       }
  674.     }
  675.     return null;
  676.   },
  677.  
  678.   /**
  679.    * Destructor function. Cleanup.
  680.    */
  681.   destroy: function TP_destroy()
  682.   {
  683.     if (this.isOpen()) {
  684.       this.close();
  685.     }
  686.  
  687.     domplateUtils.setDOM(null);
  688.  
  689.     if (this.DOMHelpers) {
  690.       this.DOMHelpers.destroy();
  691.       delete this.DOMHelpers;
  692.     }
  693.  
  694.     if (this.treePanelDiv) {
  695.       this.treePanelDiv.ownerPanel = null;
  696.       let parent = this.treePanelDiv.parentNode;
  697.       parent.removeChild(this.treePanelDiv);
  698.       delete this.treePanelDiv;
  699.       delete this.treeBrowserDocument;
  700.     }
  701.  
  702.     if (this.treeIFrame) {
  703.       this.treeIFrame.removeEventListener("keypress", this.IUI, false);
  704.       this.treeIFrame.removeEventListener("dblclick", this.onTreeDblClick, false);
  705.       this.treeIFrame.removeEventListener("click", this.onTreeClick, false);
  706.       let parent = this.treeIFrame.parentNode;
  707.       parent.removeChild(this.treeIFrame);
  708.       delete this.treeIFrame;
  709.     }
  710.  
  711.     if (this.ioBox) {
  712.       this.ioBox.destroy();
  713.       delete this.ioBox;
  714.     }
  715.  
  716.     if (!this.openInDock) {
  717.       this.container.removeEventListener("popuphiding", this._boundClose, false);
  718.       delete this._boundClose;
  719.     }
  720.   }
  721. };
  722.  
  723.  
  724. /**
  725.  * DOMHelpers
  726.  * Makes DOM traversal easier. Goes through iframes.
  727.  *
  728.  * @constructor
  729.  * @param nsIDOMWindow aWindow
  730.  *        The content window, owning the document to traverse.
  731.  */
  732. function DOMHelpers(aWindow) {
  733.   this.window = aWindow;
  734. };
  735.  
  736. DOMHelpers.prototype = {
  737.   getParentObject: function Helpers_getParentObject(node)
  738.   {
  739.     let parentNode = node ? node.parentNode : null;
  740.  
  741.     if (!parentNode) {
  742.       // Documents have no parentNode; Attr, Document, DocumentFragment, Entity,
  743.       // and Notation. top level windows have no parentNode
  744.       if (node && node == this.window.Node.DOCUMENT_NODE) {
  745.         // document type
  746.         if (node.defaultView) {
  747.           let embeddingFrame = node.defaultView.frameElement;
  748.           if (embeddingFrame)
  749.             return embeddingFrame.parentNode;
  750.         }
  751.       }
  752.       // a Document object without a parentNode or window
  753.       return null;  // top level has no parent
  754.     }
  755.  
  756.     if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) {
  757.       if (parentNode.defaultView) {
  758.         return parentNode.defaultView.frameElement;
  759.       }
  760.       // parent is document element, but no window at defaultView.
  761.       return null;
  762.     }
  763.  
  764.     if (!parentNode.localName)
  765.       return null;
  766.  
  767.     return parentNode;
  768.   },
  769.  
  770.   getChildObject: function Helpers_getChildObject(node, index, previousSibling,
  771.                                                 showTextNodesWithWhitespace)
  772.   {
  773.     if (!node)
  774.       return null;
  775.  
  776.     if (node.contentDocument) {
  777.       // then the node is a frame
  778.       if (index == 0) {
  779.         return node.contentDocument.documentElement;  // the node's HTMLElement
  780.       }
  781.       return null;
  782.     }
  783.  
  784.     if (node instanceof this.window.GetSVGDocument) {
  785.       let svgDocument = node.getSVGDocument();
  786.       if (svgDocument) {
  787.         // then the node is a frame
  788.         if (index == 0) {
  789.           return svgDocument.documentElement;  // the node's SVGElement
  790.         }
  791.         return null;
  792.       }
  793.     }
  794.  
  795.     let child = null;
  796.     if (previousSibling)  // then we are walking
  797.       child = this.getNextSibling(previousSibling);
  798.     else
  799.       child = this.getFirstChild(node);
  800.  
  801.     if (showTextNodesWithWhitespace)
  802.       return child;
  803.  
  804.     for (; child; child = this.getNextSibling(child)) {
  805.       if (!this.isWhitespaceText(child))
  806.         return child;
  807.     }
  808.  
  809.     return null;  // we have no children worth showing.
  810.   },
  811.  
  812.   getFirstChild: function Helpers_getFirstChild(node)
  813.   {
  814.     let SHOW_ALL = Components.interfaces.nsIDOMNodeFilter.SHOW_ALL;
  815.     this.treeWalker = node.ownerDocument.createTreeWalker(node,
  816.       SHOW_ALL, null, false);
  817.     return this.treeWalker.firstChild();
  818.   },
  819.  
  820.   getNextSibling: function Helpers_getNextSibling(node)
  821.   {
  822.     let next = this.treeWalker.nextSibling();
  823.  
  824.     if (!next)
  825.       delete this.treeWalker;
  826.  
  827.     return next;
  828.   },
  829.  
  830.   isWhitespaceText: function Helpers_isWhitespaceText(node)
  831.   {
  832.     return node.nodeType == this.window.Node.TEXT_NODE &&
  833.                             !/[^\s]/.exec(node.nodeValue);
  834.   },
  835.  
  836.   destroy: function Helpers_destroy()
  837.   {
  838.     delete this.window;
  839.     delete this.treeWalker;
  840.   }
  841. };
  842.